﻿/* ######################################## *\
 * ### Copyright (C) 2009 AJ Ravindiran ### *
\* ######################################## */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AJRavindiran.Jolt.RuneScape.Network.Streaming
{
    /// <summary>
    /// Represents a stream writer.
    /// </summary>
    public class StreamWriter
    {
        #region Fields
        private int writeOffset = 0;
        private byte[] outBuffer;
        private int[] frameStack;
        private int frameStackPtr = -1;
        private int frameStackSize = 10;
        private int bitPosition;
        private static int[] bitMaskOut = new int[32];
        #endregion Fields

        #region Properties
        public int WriteOffset
        {
            get { return writeOffset; }
            set { writeOffset = value; }
        }

        public byte[] OutBuffer
        {
            get { return outBuffer; }
        }
        #endregion Properties

        #region Constructors
        public StreamWriter(int outCapacity)
        {
            this.outBuffer = new byte[outCapacity];
            this.frameStack = new int[this.frameStackSize];

            for (int i = 0; i < 32; i++)
            {
                bitMaskOut[i] = (1 << i) - 1;
            }
        }
        #endregion Constructors

        #region Methods
        public StreamWriter ExpandOutBuffer()
        {
            byte[] oldBuffer = outBuffer;
            outBuffer = new byte[oldBuffer.Length + 1000];
            System.Array.Copy(oldBuffer, 0, outBuffer, 0, oldBuffer.Length);
            return this;
        }

        public StreamWriter WriteByte(int i)
        {
            if (writeOffset >= outBuffer.Length)
                ExpandOutBuffer();
            outBuffer[writeOffset++] = (byte)i;
            return this;
        }

        public StreamWriter WriteByte(int i, int position)
        {
            if (position >= outBuffer.Length)
            {
                ExpandOutBuffer();
            }
            outBuffer[position] = (byte)i;
            return this;
        }

        public StreamWriter WriteByteA(int i)
        {
            WriteByte(i + 128);
            return this;
        }

        public StreamWriter WriteByteS(int i)
        {
            WriteByte(128 - i);
            return this;
        }

        public StreamWriter WriteByteC(int i)
        {
            WriteByte(-1);
            return this;
        }

        public StreamWriter WriteBytes(byte[] array, int endOffset, int startOffset)
        {
            for (int i = startOffset; i < startOffset + endOffset; i++)
                WriteByte(array[i]);
            return this;
        }

        public StreamWriter WriteBytesS(byte[] array, int endOffset, int startOffset)
        {
            for (int i = startOffset; i < startOffset + endOffset; i++)
                WriteByte(-128 + array[i]);
            return this;
        }

        public StreamWriter WriteBytesA(byte[] array, int endOffset, int startOffset)
        {
            for (int i = startOffset; i < startOffset + endOffset; i++)
                WriteByte(array[i] - 128);
            return this;
        }

        public StreamWriter WriteBytesC(byte[] array, int endOffset, int startOffset)
        {
            for (int i = startOffset; i < startOffset + endOffset; i++)
                WriteByte(array[i] + 128);
            return this;
        }

        public StreamWriter WriteBytesReserve(byte[] array, int endOffset, int startOffset)
        {
            for (int i = (startOffset + endOffset) - 1; i >= startOffset; i--)
                WriteByte(array[i]);
            return this;
        }

        public StreamWriter WriteBytesReserveA(byte[] array, int endOffset, int startOffset)
        {
            for (int i = (startOffset + endOffset) - 1; i >= startOffset; i--)
                WriteByteA(array[i]);
            return this;
        }

        public StreamWriter WriteShortBigEndianA(int i)
        {
            WriteByte(i + 128);
            WriteByte(i >> 8);
            return this;
        }

        public StreamWriter WriteShortA(int i)
        {
            WriteByte(i >> 8);
            WriteByte(i + 128);
            return this;
        }

        public StreamWriter WriteIntV1(int i)
        {
            WriteByte(i >> 8);
            WriteByte(i);
            WriteByte(i >> 24);
            WriteByte(i >> 16);
            return this;
        }

        public StreamWriter WriteIntV2(int i)
        {
            WriteByte(i >> 16);
            WriteByte(i >> 24);
            WriteByte(i);
            WriteByte(i >> 8);
            return this;
        }

        public StreamWriter WriteVarSizeByte(int i)
        {
            if ((i & 255) >= 128)
                WriteShort(i - 32768);
            else
                WriteByte(i);
            return this;
        }

        public StreamWriter WriteShort(int i)
        {
            WriteByte(i >> 8);
            WriteByte(i);
            return this;
        }

        public StreamWriter WriteShortBigEndian(int i)
        {
            WriteByte(i);
            WriteByte(i >> 8);
            return this;
        }

        public StreamWriter Write3Byte(int i)
        {
            WriteByte(i >> 16);
            WriteByte(i >> 8);
            WriteByte(i);
            return this;
        }

        public StreamWriter WriteInt(int i)
        {
            WriteByte(i >> 24);
            WriteByte(i >> 16);
            WriteByte(i >> 8);
            WriteByte(i);
            return this;
        }

        public StreamWriter WriteIntBigEndian(int i)
        {
            WriteByte(i);
            WriteByte(i >> 8);
            WriteByte(i >> 16);
            WriteByte(i >> 24);
            return this;
        }

        public StreamWriter WriteLong(long l)
        {
            WriteByte((int)(l >> 56));
            WriteByte((int)(l >> 48));
            WriteByte((int)(l >> 40));
            WriteByte((int)(l >> 32));
            WriteByte((int)(l >> 24));
            WriteByte((int)(l >> 16));
            WriteByte((int)(l >> 8));
            WriteByte((int)l);
            return this;
        }

        public StreamWriter WriteString(string s)
        {
            byte[] stringBytes = new ASCIIEncoding().GetBytes(s);
            for (int i = 0; i < s.Length; i++)
                WriteByte(stringBytes[i]);
            WriteByte(0);
            return this;
        }

        public StreamWriter CreateFrame(int id)
        {
            WriteByte(id);
            return this;
        }

        public StreamWriter CreateFrameVarSize(int id)
        {
            WriteByte(id);
            WriteByte(0);
            if (frameStackPtr >= frameStackSize - 1)
                throw new PlayerSocketException("Stack overflow.");
            else
                frameStack[++frameStackPtr] = writeOffset;
            return this;
        }

        public StreamWriter CreateFrameVarSizeShort(int id)
        {
            WriteByte(id);
            WriteShort(0);
            if (frameStackPtr >= frameStackSize - 1)
                throw new PlayerSocketException("Stack overflow.");
            else
                frameStack[++frameStackPtr] = writeOffset;
            return this;
        }

        public StreamWriter EndFrameVarSize()
        {
            if (frameStackPtr < 0)
                throw new PlayerSocketException("Stack empty");
            else
                WriteFrameSize(writeOffset - frameStack[frameStackPtr--]);
            return this;
        }

        public StreamWriter EndFrameVarSizeShort()
        {
            if (frameStackPtr < 0)
                throw new PlayerSocketException("Stack empty");
            else
                WriteFrameSizeShort(writeOffset - frameStack[frameStackPtr--]);
            return this;
        }

        public StreamWriter WriteFrameSize(int i)
        {
            WriteByte(i, (writeOffset - i - 1));
            return this;
        }

        public StreamWriter WriteFrameSizeShort(int i)
        {
            WriteByte((i >> 8), (writeOffset - i - 2));
            WriteByte(i, (writeOffset - i - 1));
            return this;
        }

        public StreamWriter InitializeBitAccess()
        {
            bitPosition = writeOffset * 8;
            return this;
        }

        public StreamWriter FinishBitAccess()
        {
            writeOffset = (bitPosition + 7) / 8;
            return this;
        }

        public StreamWriter AddBit(int bit, int position)
        {
            if (position >= outBuffer.Length)
                ExpandOutBuffer();
            outBuffer[position] |= (byte)bit;
            return this;
        }

        public StreamWriter PlaceBit(int bit, int position)
        {
            if (position >= outBuffer.Length)
                ExpandOutBuffer();
            outBuffer[position] |= (byte)bit;
            return this;
        }

        public StreamWriter WriteBits(int numBits, int value)
        {
            int bytePos = bitPosition >> 3;
            int bitOffset = 8 - (bitPosition & 7);
            bitPosition += numBits;
            for (; numBits > bitOffset; bitOffset = 8)
            {
                AddBit(bitMaskOut[bitOffset], bytePos);
                PlaceBit(((value >> (numBits - bitOffset)) & bitMaskOut[bitOffset]), bytePos++);
                numBits -= bitOffset;
            }
            if (numBits == bitOffset)
            {
                AddBit(bitMaskOut[bitOffset], bytePos);
                PlaceBit((value & bitMaskOut[bitOffset]), bytePos);
            }
            else
            {
                AddBit((bitMaskOut[numBits] << (bitOffset - numBits)), bytePos);
                PlaceBit((value & bitMaskOut[numBits]) << (bitOffset - numBits), bytePos);
            }
            return this;
        }
        #endregion Methods
    }
}
